package main

import (
	"fmt"
	"sync"
	"time"
)

/*
	WaitGroup，同步等待组
    一个WaitGroup的用途是等待一个goroutine的集合执行完成。
    主goroutine调用了Add()方法来设置要等待的goroutine的数量。
    然后，每个goroutine都会执行并且执行完成后调用Done()这个方法。
    与此同时，可以使用Wait()方法来阻塞，直到所有的goroutine都执行完成。

    Add()方法:
    Add这个方法，用来设置到WaitGroup的计数器的值.

    Done()方法:
    Done()方法，就是当WaitGroup同步等待组中的某个goroutine执行完毕后，设置这个WaitGroup的counter数值减1。

    Wait()方法:
    Wait()方法，表示让当前的goroutine等待，进入阻塞状态。一直到WaitGroup的计数器为零。才能解除阻塞，这个goroutine才能继续执行。

sync.WaitGroup类型值中计数器的值可以小于0吗？
	不可以
	WaitGroup值中计数器的值不能小于0，是因为这样会引发一个 panic。 不适当地调用这类值的Done方法和Add方法都会如此。

	不要把增加其计数器值的操作和调用其Wait方法的代码，放在不同的 goroutine 中执行。
	换句话说，要杜绝对同一个WaitGroup值的两种操作的并发执行。


知识扩展
问题：sync.Once类型值的Do方法是怎么保证只执行参数函数一次的？
	与sync.WaitGroup类型一样，sync.Once类型（以下简称Once类型）也属于结构体类型，同样也是开箱即用和并发安全的。
	Once类型的Do方法只接受一个参数，这个参数的类型必须是func()，即：无参数声明和结果声明的函数。
	如果有多个只需要执行一次的函数，那么就应该为它们中的每一个都分配一个sync.Once类型的值（以下简称Once值）。

Once类型中还有一个名叫done的uint32类型的字段。它的作用是记录其所属值的Do方法被调用的次数。
不过，该字段的值只可能是0或者1。一旦Do方法的首次调用完成，它的值就会从0变为1。

Do方法在功能方面的两个特点:
	第一个特点，由于Do方法只会在参数函数执行结束之后把done字段的值变为1，
	因此，如果参数函数的执行需要很长时间或者根本就不会结束（比如执行一些守护任务），那么就有可能会导致相关 goroutine 的同时阻塞。

	第二个特点，Do方法在参数函数执行结束后，对done字段的赋值用的是原子操作，并且，这一操作是被挂在defer语句中的。
	因此，不论参数函数的执行会以怎样的方式结束，done字段的值都会变为1。
 */

// 创建同步等待组对象
var wg sync.WaitGroup

var once sync.Once
func main() {

	/*
	   WaitGroup：同步等待组
	       可以使用Add(),设置等待组中要 执行的子goroutine的数量，

	       在main 函数中，使用wait(),让主程序处于等待状态。直到等待组中子程序执行完毕。解除阻塞

	       子gorotuine对应的函数中。wg.Done()，用于让等待组中的子程序的数量减1
	*/

	//设置等待组中，要执行的goroutine的数量
	wg.Add(2)
	go fun01()
	go fun02()
	fmt.Println("main进入阻塞状态.... 等待wg中的子goroutine结束....")
	//表示main goroutine进入等待，意味着阻塞
	wg.Wait()
	fmt.Println("main，解除阻塞....")

	for i:=0; i<10; i++{
         go readConfig()
	}

	time.Sleep(time.Millisecond * 100)

	
}

func fun01()  {
	//给wg等待中的执行的goroutine数量减1.同Add(-1)
	defer wg.Done()
	for i:=1;i<=10;i++{
		fmt.Println("fun01...i:",i)
	}
}

func fun02()  {
	defer wg.Done()
	for j:=1;j<=10;j++{
		fmt.Println("\tfun02...j,",j)
	}
}

/*
	测试sync.Once的 Do()方法，只会执行一次
 */
func readConfig() {
	once.Do(func() {
		// 读取配置文件
		fmt.Println("Read config over......")
	})

}
